<?php

namespace app\common\logic;

use app\admin\logic\finance\Encashment;
use app\admin\logic\system\Config as SystemConfig;
use app\common\command\decrypt\Des;
use app\common\exception\LogicException;
use app\common\library\Queue;
use app\common\model\payment\PaymentOrder as PO;
use app\common\model\ProductType;
use app\course\logic\base\{Course, CourseSection, CourseStudent};
use app\exam\logic\{Paper, Rollup, Special};
use app\mall\logic\product\Product;
use app\user\event\subscribes\user\Expenditure;
use app\user\logic\user\{Balance, BalanceFlow, Vip};
use eduline\payment\Pay;
use Exception;
use think\facade\{Event, Log, Request};
use think\facade\Db;
use Yansongda\Pay\Exceptions\BusinessException;
use Yansongda\Pay\Gateways\{Wechat};

/**
 * 支付
 */
class Payment
{
    /* 支付方式及订单状态 start */

    /**
     * 开通的支付方式
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-05-06
     */
    public function payway($lcn = true)
    {
        $payway = SystemConfig::get('system.package.payment', []);

        // if ($lcn) array_unshift($payway, 'lcnpay');

        $in = array_search('applepay', $payway);

        if (strtolower(request()->device_type) != 'ios' && $in !== false) unset($payway[$in]);

        return array_values($payway);
    }

    /**
     * 开通的支付方式及余额
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-05-06
     */
    public function orderPayFetch(): array
    {
        $return['payway']       = $this->payway();
        $return['balance']      = app(Balance::class)->userBalance();
        $return['balpwd_setus'] = app(Balance::class)->userBalancePwdSetStatus();

        return $return;
    }

    // 检查三方支付订单状态
    public function orderquery($param)
    {
        // 参数
        $pay_type = $param['pay_type'] ?? '';
        $order_no = $param['order_no'] ?? '';
        if (!in_array($pay_type, ['alipay', 'wxpay'])) throw new LogicException('参数错误');

        $func = 'orderquery' . ucfirst($pay_type);
        if (method_exists($this, $func)) {
            return $this->$func($order_no);
        } else {
            throw new LogicException('系统未定义当前支付类型');
        }
    }

    public function orderqueryAlipay($order_no): array
    {
        $res          = Pay::alipay()->find($order_no);
        $result_state = $res->trade_status;
        switch ($result_state) {
            case 'WAIT_BUYER_PAY':
                $result_msg = '交易创建，等待买家付款';
                break;
            case 'TRADE_CLOSED':
                $result_msg = '交易关闭';
                break;
            case 'TRADE_SUCCESS':
                $result_msg = '交易支付成功';
                break;
            case 'TRADE_FINISHED':
            default:
                $result_msg = '交易结束，不可退款';
                break;
        }

        return ['result_state' => $result_state, 'result_msg' => $result_msg];
    }

    public function orderqueryWxpay($order_no): array
    {
        $res          = Pay::wxpay()->find($order_no);
        $result_state = 'FAIL';
        $result_msg   = 'FAIL';
        if ('FAIL' == $res->return_code) {
            $result_msg = $res->return_msg;
        } else if ('SUCCESS' == $res->return_code) {
            if ('FAIL' == $res->result_code) {
                $result_msg = $res->err_code_des;
            } else if ('SUCCESS' == $res->result_code) {
                $result_state = $res->trade_state;
                switch ($res->trade_state) {
                    case 'SUCCESS':
                        $result_msg = '支付成功';
                        break;
                    case 'REFUND':
                        $result_msg = '转入退款';
                        break;
                    case 'NOTPAY':
                        $result_msg = '未支付';
                        break;
                    case 'CLOSED':
                        $result_msg = '已关闭';
                        break;
                    case 'REVOKED':
                        $result_msg = '已撤销';
                        break;
                    case 'USERPAYING':
                        $result_msg = '用户支付中';
                        break;
                    case 'PAYERROR':
                    default:
                        $result_msg = '支付失败';
                        break;
                }
            }
        }

        return [
            'result_state' => $result_state,
            'result_msg'   => $result_msg
        ];
    }

    /* 支付方式及订单状态 end */

    /* 支付及回调 start */

    /**
     * 支付
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-05-06
     *
     */
    public function doPay($param)
    {
        // 参数
        $payway   = $param['pay_type'] ?? '';
        $order_no = $param['order_no'] ?? '';
        // 数据验证
        if (!$payway || !$order_no || !in_array($payway, ['alipay', 'wxpay', 'lcnpay', 'paypal'])) {
            throw new LogicException('参数错误');
        }
        // 订单支付状态
        $order = app(PO::class)->with(['itemMany'])->where('order_no', $order_no)->findOrFail();

        if (in_array($order->status, [20, 30, 40, 80])) return $order;

        if (in_array($order->status, [0, 50, 60, 70])) throw new LogicException('订单不可用');

        if ($order->getAttr('close_time') != 0 && $order->getAttr('close_time') < Request::time()) throw new LogicException('订单已失效');
        // 存在非单卖课程
        foreach ($order->itemMany as $i) {
            if (in_array($i->product_type_id, [1, 2, 3, 4])) $ids[] = $i->product_id;
        }
        if (isset($ids) && count($ids)) {
            $solo = app(Course::class)->whereIn('id', $ids)->where('solo', 0)->count();
            if ($solo) throw new LogicException('存在不可用产品');
        }
        // 检查是否课程已购买
        $this->checkCourseBuy($order);
        // 小班课人数限制检测
        $this->checkSmallLiveLimit($order);
        // 支付
        if (method_exists($this, $payway)) {
            return $this->$payway($order, $param);
        } else {
            throw new LogicException('该支付暂未开放');
        }
        // 支付完成后，支付状态、分成及明细等事件处理
    }

    // 检查课程购买
    public function checkCourseBuy($order)
    {
        $item = $order->itemMany->first(); //$order->itemMany->last()
        if (in_array($item->product_type_id, [1, 2, 3, 4])) {

            // 课程id
            $course_id = $item->getAttr('product_id');
            //app(CourseSection::class)->where('id', $item->product_id)->value('course_id');

            // 课程是否购买
            // $order_no = app(POI::class)
            //     ->scope('mid')
            //     ->where('product_id', $course_id)
            //     ->where('product_type_id', 1)
            //     ->value('order_no');
            $buy = app(CourseStudent::class)->isBuy($course_id);

            if ($buy) {
                throw new LogicException('已购买课程，无需重复购买');
            }
        }
    }

    // 检测小班课购买人数限制
    public function checkSmallLiveLimit($order)
    {
        foreach ($order->itemMany as $item) {
            if (in_array($item->product_type_id, [1, 2, 3, 4])) {
                // 课程id
                $course_id = $item->getAttr('product_id');
                $course    = app(Course::class)->findOrFail($course_id);

                if ($course->limit_buy_count) {//$course->course_type == 2 &&
                    $count = app(CourseStudent::class)->where('course_id', $course->id)->count();
                    if ($count >= $course->limit_buy_count) {
                        throw new LogicException('课程已限制购买');
                    }
                }
            }
        }
    }

    /**
     * 余额支付
     * Author  亓官雨树<lucky.max@foxmail.com>
     * Date：    2020-05-07
     */
    private function lcnpay($order, $param)
    {
        Db::startTrans();
        try {
            // 生成交易记录
            app(Transaction::class)->addTransaction($order, 'lcnpay');
            //
            if (request()->user->phone) {
                $password = $param['password'] ?? '';
                // 检查余额支付密码
                $pwd = app(Balance::class)->wherePassword($password)->whereUserId(request()->mid)->findOrEmpty();
                if ($pwd->isEmpty()) throw new LogicException('支付密码错误');
            }
            // 检查余额
            $balance = app(Balance::class)->userBalance();
            if ($balance < $order->payment) throw new LogicException('余额不足');
            // 检查重复支付
            $exist = app(BalanceFlow::class)->where('rel_pk', $order->order_no)->findOrEmpty();
            if ($exist->isEmpty()) {
                $type_note = $this->getAlterTypeAndNote($order);
                // 非余额充值，执行余额扣除及余额流水，后续调整为事件处理
                if (ProductType::getProductIdByKey('balance') != $type_note['alter_type']) {
                    /* 参数说明【消费用户ID、余额消费类型、消费金额、消费关联表、关联表ID、消费备注】 */
                    app(Balance::class)->decreaseUserBalance(request()->mid, $type_note['alter_type'], $order->payment, 'payment_order', $order->order_no, $type_note['note']);
                    // 用户消费记录
                    $this->userExpenditure($order);
                    // 交易记录表 交易时间
                    app(Transaction::class)->notify($order->order_no);
                    // 队列处理支付状态、分成及明细等
                    Queue::push('AfterPay', ['order_no' => $order->order_no]);
                }
            } else {
                // 已经支付过了,这里处理后续: 队列处理支付状态、分成及明细等
                Queue::push('AfterPay', ['order_no' => $order->order_no]);
            }
            Db::commit();

            return $order;
        } catch (LogicException|Exception $e) {
            Db::rollback();
            throw new LogicException($e->getMessage());
        }
    }

    /**
     * 类型及备注
     * 4:购买课程；5:购买课时；6:充值会员；7:充值余额；8：充值积分；9：购买考试；10：购买商品；
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-09
     */
    private function getAlterTypeAndNote($order): array
    {
        $course_title = '';
        $alter_type   = '';
        $note         = '';
        $_vip         = ProductType::getProductIdByKey('vip');
        $_balance     = ProductType::getProductIdByKey('balance');
        $_credit      = ProductType::getProductIdByKey('credit');
        $_exam        = ProductType::getProductIdByKey('exam');
        $_special     = ProductType::getProductIdByKey('special');
        $_rollup      = ProductType::getProductIdByKey('rollup');
        $_goods       = ProductType::getProductIdByKey('goods');

        foreach ($order->itemMany as $item) {
            if ($item->getAttr('section_id') > 0) {
                // 购买课时
                $alter_type = 4;
                $section    = app(CourseSection::class)->with(['courseInfo'])->findOrFail($item->section_id);
                $note       = '购买课时：【' . $section->courseInfo->title . '】' . $section->title;
            } else {
                switch ($item->product_type_id) {
                    case $_vip: // 会员
                        $alter_type = $_vip;
                        $vip_title  = app(Vip::class)->where('id', $item->product_id)->value('title');
                        $note       = '充值会员：' . $vip_title;
                        break;

                    case $_balance: // 余额
                        $alter_type = $_balance;
                        $note       = '充值余额：' . $item->original_price;
                        break;

                    case $_credit: // 积分
                        $alter_type = $_credit;
                        $note       = '充值积分：' . $item->original_price . '分';
                        break;

                    case $_exam: // 考试
                        $alter_type = $_exam;
                        $title      = app(Paper::class)->getTitleById($item->product_id);
                        $note       = '购买公开考试：【' . $title . '】';
                        break;

                    case $_special: // 专项
                        $alter_type = $_special;
                        $title      = app(Special::class)->getTitleById($item->product_id);
                        $note       = '购买专项练习：【' . $title . '】';

                        break;

                    case $_rollup: // 套卷
                        $alter_type = $_rollup;
                        $title      = app(Rollup::class)->getTitleById($item->product_id);
                        $note       = '购买套卷：【' . $title . '】';

                        break;

                    case $_goods: // 商品
                        $alter_type = $_goods;
                        $title      = app(Product::class)->getTitleById($item->product_id);
                        $note       = '购买商品：【' . $title . '】';

                        break;

                    case ProductType::getProductIdByKey('video'):
                    case ProductType::getProductIdByKey('live'):
                    case ProductType::getProductIdByKey('offline'):
                    case ProductType::getProductIdByKey('classes'):
                        $alter_type   = 3;
                        $title        = app(Course::class)->where('id', $item->product_id)->value('title');
                        $course_title .= '【' . $title . '】';
                        $note         = '购买课程：' . $course_title;
                        break;
                }
            }

        }

        return ['alter_type' => $alter_type, 'note' => mb_substr($note, 0, 20, 'utf-8')];
    }

    private function alipay($order, $param)
    {
        //
        $return_url = $param['return_url'] ?? '';
        // $api_host   = Config::get('eduline.appinfo.api_host', '');
        $api_host = Request::domain();
        // 支付终端类型
        $device_type = ['pc' => 'web', 'ios' => 'app', 'android' => 'app', 'h5' => 'wap', 'miniprogram' => 'mini'][request()->device_type];
        //设置支付的Data信息
        $type_note  = $this->getAlterTypeAndNote($order);
        $subject    = $type_note['note'];
        $bizcontent = [
            'out_trade_no' => $order->order_no,
            'total_amount' => strval($order->payment),
            'subject'      => strval($subject),
            'return_url'   => strval($return_url),
            'notify_url'   => $api_host . '/payback/alipay',
        ];

        // Db::name('test')->save(['msg' => Pay::alipay()->$device_type($bizcontent)->getContent()]);

        // 生成交易记录
        app(Transaction::class)->addTransaction($order, 'alipay');

        // 支付
        try {
            return Pay::alipay()->$device_type($bizcontent)->getContent();
        } catch (Exception $e) {
            throw new LogicException('支付配置错误，请联系商家');
	    //throw new LogicException($e->getMessage());
        }
    }

    /**
     * 阿里支付回调
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-09
     */
    public function alipayNotify()
    {
        // Db::name('test')->save(['msg'=>"alipayNotify",'create_time'=>time()]);
        $alipay = Pay::alipay();
        try {
            $data = $alipay->verify();
            // 校验
            $this->aliVerify($data);
            // 交易记录表状态
            app(Transaction::class)->notify($data->out_trade_no, $data->trade_no);
            // 用户消费记录
            $order = app(PO::class)->with(['itemMany'])->where('order_no', $data->out_trade_no)->where('status', 10)->findOrFail();
            $this->userExpenditure($order);
            // 队列处理支付状态、分成及明细等
            Queue::push('AfterPay', ['order_no' => $data->out_trade_no]);
            // Log::debug('Alipay notify', $data->all());
        } catch (LogicException|Exception $e) {
            throw new LogicException($e->getMessage());
        }

        return $alipay->success()->send();
    }

    // 阿里回调验证 trans为true时表示提现
    public function aliVerify($data, $trans = false)
    {
        if (!in_array($data->trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
            Log::info('alipayNotifyError交易失败');
            throw new LogicException('交易失败');
        }
        if ($trans) {
            $order = app(Encashment::class)->where('order_no', $data->out_trade_no)->findOrEmpty();
        } else {
            $order = app(PO::class)->where('order_no', $data->out_trade_no)->findOrEmpty();
        }
        if ($order->isEmpty()) {
            Log::info('alipayNotifyError订单查询失败');
            throw new LogicException('订单查询失败');
        }
        if ($order->payment != $data->total_amount) {
            Log::info('alipayNotifyError交易金额错误');
            throw new LogicException('交易金额错误');
        }
        $ali_conf = SystemConfig::get('system.package.payment.alipay', ['app_id' => ''], request()->mhm_id);
        if ($data->app_id != $ali_conf['app_id']) {
            Log::info('alipayNotifyError商户号错误');
            throw new LogicException('商户号错误');
        }
        // 3、校验通知中的seller_id（或者seller_email) 是否为out_trade_no这笔单据的对应的操作方（有的时候，一个商户可能有多个seller_id/seller_email）；
        // 5、其它业务逻辑情况
    }

    /**
     * 微信支付
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-10
     *
     * @param  [type]                      $order  [description]
     * @param  [type]                      $openid [description]
     */
    private function wxpay($order, $param)
    {
        $from   = request()->device_type;
        $device = ['pc' => '电脑', 'ios' => 'app', 'android' => 'app', 'h5' => '手机网页', 'miniprogram' => '小程序'][$from];
        if ($from != $order->from) throw new LogicException('请使用' . $device . '端支付或重新下单');
        // 支付终端类型
        $device_type = ['pc' => 'scan', 'ios' => 'app', 'android' => 'app', 'h5' => 'wap', 'miniprogram' => 'miniapp'][$from];
        // 参数
        $total_fee  = $order->payment * 100;
        $type_note  = $this->getAlterTypeAndNote($order);
        $body       = $type_note['note'];
        $attributes = [
            'out_trade_no' => $order->order_no,
            'total_fee'    => strval($total_fee), // **单位：分**
            'body'         => strval($body),
            'notify_url'   => Request::domain() . '/payback/wxpay',
        ];
        // 生成交易记录
        app(Transaction::class)->addTransaction($order, 'wxpay');

        try {
            switch ($device_type) {
                case 'scan':
                    return Pay::wxpay()->scan($attributes);
                case 'wap':
                    $openid = isset($param['openid']) ? Des::jiemi($param['openid']) : '';
                    if ($openid) {
                        $attributes['openid'] = $openid;
                        $re                   = Pay::wxpay()->mp($attributes);
                    } else {
                        $re = Pay::wxpay()->wap($attributes)->getContent();
                        preg_match('/<a href=\"(.*?)\".*?>(.*?)<\/a>/i', $re, $matches);
                        // $re = $matches[1];
                        $re = str_replace('&amp;', '&', $matches[1]);
                    }
                    return $re;
                case 'miniapp':
                    $attributes['openid'] = isset($param['openid']) ? Des::jiemi($param['openid']) : '';
                    return Pay::wxpay()->miniapp($attributes);
                default:
                    return Pay::wxpay()->$device_type($attributes)->getContent();
            }
        } catch (BusinessException|Exception $e) {
            throw new LogicException('微信配置错误，请联系商家');
        }
    }

    /**
     * 微信支付回调
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-06-09
     *
     * @throws Exception
     */
    public function wxpayNotify()
    {
        $wxpay = Pay::wxpay();
        try {
            $data = $wxpay->verify();
            // 交易记录表状态
            app(Transaction::class)->notify($data->out_trade_no, $data->transaction_id);
            // 用户消费记录
            $order = app(PO::class)->with(['itemMany'])->where('order_no', $data->out_trade_no)->findOrFail();
            $this->userExpenditure($order);
            // 队列处理支付状态、分成及明细等
            Queue::push('AfterPay', ['order_no' => $data->out_trade_no]);
        } catch (Exception $e) {
            throw new Exception($e->getMessage());
        }

        return $wxpay->success()->send();
    }

    /**
     * 用户消费记录
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-08-06
     */
    protected function userExpenditure($order)
    {
        foreach ($order->itemMany as $item) {
            $product_key = ProductType::getProductKeyById($item->product_type_id);
            $title       = '';
            switch ($product_key) {
                case 'video':
                case 'live':
                case 'offline':
                case 'classes':
                    $title = app(Course::class)->getTitleById($item->product_id);
                    break;

                case 'exam':
                    $title = app(Paper::class)->getTitleById($item->product_id);
                    break;

                case 'special':
                    $title = app(Special::class)->getTitleById($item->product_id);
                    break;

                case 'rollup':
                    $title = app(Rollup::class)->getTitleById($item->product_id);
                    break;

                case 'goods':
                    $title = app(Product::class)->getTitleById($item->product_id);
                    break;

                default:
                    # code...
                    break;
            }

            $note_arr = [
                'video'   => '购买点播课程《' . $title . '》',
                'live'    => '购买直播课程《' . $title . '》',
                'offline' => '购买面授课程《' . $title . '》',
                'classes' => '购买班级课程《' . $title . '》',
                'vip'     => '购买会员',
                'balance' => '充值余额',
                'credit'  => '充值积分',
                'exam'    => '购买公开考试《' . $title . '》',
                'goods'   => '购买商品《' . $title . '》',
                'special' => '购买专项练习《' . $title . '》',
                'rollup'  => '购买套卷《' . $title . '》',

            ];

            $param = [
                'order_sub_no' => $item->order_sub_no,
                'user_id'      => $item->user_id,
                'num'          => $item->payment,
                'product'      => $product_key,
                'rel_id'       => $item->product_id,
                'note'         => $note_arr[$product_key],
            ];

            Event::subscribe(Expenditure::class);
            Event::trigger('Expenditure', $param);
        }
    }

    /* 支付及回调 end */

    /* 三方转账 start */

    public function alitrans(Encashment $order)
    {
        $bizcontent = [
            'out_biz_no'   => $order->order_no,
            'trans_amount' => $order->money,
            'product_code' => 'TRANS_ACCOUNT_NO_PWD',
            'biz_scene'    => 'DIRECT_TRANSFER',
            'payee_info'   => [
                'identity'      => $order->alipay_account,
                // "identity_type"   => "ALIPAY_USER_ID",
                'identity_type' => 'ALIPAY_LOGON_ID',
                'name'          => $order->alipay_user_name,
            ],
            'remark'       => "收入提现到支付宝：$order->money",
        ];
        try {
            Pay::alipay()->transfer($bizcontent);
        } catch (Exception $e) {
            $order->status = 1;
            $order->save();
            $error = isset($e->raw) ? $e->raw['alipay_fund_trans_uni_transfer_response']['sub_msg'] : $e->getMessage();
            throw new LogicException($error);
        }
    }

    public function wxtrans(Encashment $order)
    {
        $type_arr   = ['android' => 'app', 'ios' => 'app', 'pc' => '', 'miniprogram' => 'miniapp', 'h5' => ''];
        $type       = $type_arr[$order->wxpay_device_type] ?? '';
        $total_fee  = $order->money * 100;
        $attributes = [
            'type'             => $type, // 转账端别对应的openid
            'partner_trade_no' => $order->order_no, // 订单号。
            'openid'           => $order->wxpay_openid, // 收款人的openid
            'check_name'       => 'NO_CHECK', // OPTION_CHECK FORCE_CHECK
            'amount'           => $total_fee, // 单位为分
            'desc'             => "收入提现到微信：$order->money",
        ];

        try {
            /** @var Wechat $wxpay */
            $wxpay = Pay::wxpay();
            $wxpay->transfer($attributes);
        } catch (Exception $e) {
            $order->status = 1;
            $order->save();
            $error = isset($e->raw) ? $e->raw['err_code_des'] : $e->getMessage();
            throw new LogicException($error);
        }
    }

    /* 三方转账 end */

}
